package com.atom.module.mvi.core.bootstrapper


import com.atom.module.mvi.binder.middleware.base.Middleware
import com.atom.module.mvi.binder.middleware.config.MiddlewareConfiguration
import com.atom.module.mvi.binder.middleware.config.Middlewares
import com.atom.module.mvi.binder.middleware.config.WrappingCondition
import com.atom.module.mvi.core.element.Bootstrapper
import com.atom.module.mvi.core.feature.BaseFeature
import com.atom.module.mvi.core.feature.Feature
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.observers.TestObserver
import io.reactivex.rxjava3.subjects.ReplaySubject
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.*
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock

class BootstrapperTest {

    private sealed class Action {
        object Action1 : Action()
        object Action2 : Action()
        object Action3 : Action()
    }

    private lateinit var feature: Feature<Any, Any, Any>
    private lateinit var actionHandler: TestObserver<Action>

    @After
    fun tearDown() {
        Middlewares.configurations.clear()
    }

    @Test
    fun `GIVEN Feature without Bootstrapper THEN Bootstrapper doesn't emit actions`() {
        initializeFeatureWithBootstrapper(null)

        actionHandler.assertEmpty()
    }

    @Test
    fun `GIVEN Feature with Bootstrapper WHEN Bootstrapper doesn't emit actions THEN no actions caught`() {
        val bootstrapper: Bootstrapper<Action> = {
            Observable.empty()
        }
        initializeFeatureWithBootstrapper(bootstrapper)

        actionHandler.assertEmpty()
    }

    @Test
    fun `GIVEN Feature with Bootstrapper THEN Bootstrapper executes only ones`() {
        var counter = 0
        val bootstrapper = {
            counter++
            Observable.empty<Action>()
        }
        initializeFeatureWithBootstrapper(bootstrapper)

        listOf(1, 2, 3).forEach(feature::accept)

        assertEquals(counter, 1)
    }

    @Test
    fun `GIVEN Feature with Bootstrapper WHEN Bootstrapper emits actions THEN the order of actions remains unchanged`() {
        val bootstrapper = {
            Observable.just(Action.Action1, Action.Action2, Action.Action1, Action.Action3)
        }
        initializeFeatureWithBootstrapper(bootstrapper)

        actionHandler.assertValues(Action.Action1, Action.Action2, Action.Action1, Action.Action3)
    }

    @Test
    fun `GIVEN Feature with Bootstrapper WHEN Bootstrapper emits actions THEN they propagates to Bootstrapper Middleware`() {
        val testMiddleware = setupTestMiddlewareConfiguration()
        val bootstrapper = {
            Observable.just(Action.Action2, Action.Action3, Action.Action1)
        }
        initializeFeatureWithBootstrapper(bootstrapper)

        with(argumentCaptor<Action>()) {
            verify(testMiddleware, times(3)).onElement(any(), capture())
            assertEquals(listOf(Action.Action2, Action.Action3, Action.Action1), allValues)
        }
    }

    private fun setupTestMiddlewareConfiguration(): Middleware<Any, Action> {
        val middlewareStub = spy(object : Middleware<Any, Action>(mock()) {})

        Middlewares.configurations.add(
            MiddlewareConfiguration(
                condition = WrappingCondition.Always,
                factories = listOf { _ -> middlewareStub }
            )
        )

        return middlewareStub
    }

    private fun initializeFeatureWithBootstrapper(bootstrapper: Bootstrapper<Action>?) {
        val actions = ReplaySubject.create<Action>()
        feature = BaseFeature<Any, Action, Any, Any, Any>(
            initialState = Any(),
            bootstrapper = bootstrapper,
            wishToAction = { _ -> Action.Action1 },
            actor = { _, action ->
                actions.onNext(action)
                Observable.empty()
            },
            reducer = { _, _ -> Any() },
            postProcessor = null,
            newsPublisher = null
        )

        actionHandler = actions.test()
    }
}
